﻿// The MIT License (MIT)
// Copyright (c) 2014 LuoZhihui
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the “Software”), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;

namespace EasyTelnet
{
    /// <summary>
    ///     Telnet 用户接口
    /// </summary>
    public abstract class TelnetSessionBase : IDisposable
    {
        internal readonly DataReadWrite Traffic;
        private readonly int _receiveTimeOut;

        /// <summary>
        /// Telnet登陆凭据
        /// </summary>
        protected Credentials Credentials { get; set; }

        /// <summary>
        ///已经登陆成功
        /// </summary>
        protected bool IsLogin { get; set; }

        /// <summary>
        ///     虚拟终端
        /// </summary>
        protected NetVirtualTerminal NetVirtualTerminal;

        /// <summary>
        ///     接收为byte数组
        /// </summary>
        public  Action<byte[]> ReceivedBytes;

        /// <summary>
        ///     接收为字符串
        /// </summary>
        public  Action<string> ReceivedString;


        /// <summary>
        ///     当前获取到的数据
        /// </summary>
        public StringBuilder TelnetData = new StringBuilder();

        protected TelnetSessionBase(string host, int port, int receiveTimeout, int sendTimeout, Credentials credentials,NetVirtualTerminal netVirtualTerminal)
        {
            _receiveTimeOut = receiveTimeout;
            Traffic = new DataReadWrite(host, port, receiveTimeout, sendTimeout);
            NetVirtualTerminal = netVirtualTerminal;
            Credentials = credentials;
            Traffic.TelnetProtocolAnalyzer += TelnetProtocolAnalyzeHandler;
            Traffic.DataAvailable += DataAvailableEventHandler;
        }

        /// <summary>
        ///     收到数据处理程序
        /// </summary>
        /// <param name="args"></param>
        protected virtual void DataAvailableEventHandler(DataAvailableEventArgments args)
        {
            lock (NetVirtualTerminal)
            {
                TelnetData.Clear();
                if (!args.HasData) return;

                var buffer = args.Data.ToArray();
                if (ReceivedBytes != null)
                {
                    ReceivedBytes(buffer);
                }

                var originStr = NetVirtualTerminal.TerminalEncoding.GetString(buffer);
                TelnetData.Append(originStr);
                if (ReceivedString != null)
                {
                    ReceivedString(originStr);
                }

                CredentialsHook(args);

                SessionHook(args);
            }
        }

        /// <summary>
        /// 会话钩子
        /// </summary>
        /// <param name="args"></param>
        protected virtual void SessionHook(DataAvailableEventArgments args) { }

        /// <summary>
        /// 启动会话
        /// </summary>
        public virtual void SessionStart()
        {
            Traffic.Connect();
        }
        
        /// <summary>
        /// 凭据钩子
        /// </summary>
        /// <param name="args"></param>
        protected void CredentialsHook(DataAvailableEventArgments args)
        {
            if (IsLogin || Credentials == null) return;

            var commandPrompt = FindData(Credentials.CommandPrompt, false);
            if (!string.IsNullOrEmpty(commandPrompt))
            {
                IsLogin = true;
            }
            else
            {
                var loginPrompt = FindData(Credentials.UsernamePrompt, false);
                if (!string.IsNullOrEmpty(loginPrompt))
                {
                    SendAsync(Credentials.Username, true);
                }

                var passwordPrompt = FindData(Credentials.PasswordPrompt, false);
                if (!string.IsNullOrEmpty(passwordPrompt))
                {
                    SendAsync(Credentials.Password, true);
                }
            }
        }

        /// <summary>
        ///     Telnet协议解析处理程序
        /// </summary>
        /// <param name="receivedStream"></param>
        private DataAvailableEventArgments TelnetProtocolAnalyzeHandler(DataStream receivedStream)
        {
            var analyze = new TelnetProtocolAnalyzer(NetVirtualTerminal);
            return analyze.ProtocolAnalyze(receivedStream);
        }

        public override string ToString()
        {
            return TelnetData.ToString();
        }


        #region IDisposable 成员

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            { 
               Traffic.Dispose();
            } 
        }

        public void Dispose()
        {
            Dispose(true); 
            GC.SuppressFinalize(this);
        }

        #endregion


        #region 发送一个响应到服务器

        /// <summary>
        ///     发送一个响应到服务器
        /// </summary>
        /// <param name="response">发送内容</param>
        /// <param name="endLine">发送结束符</param>
        /// <returns>true if sending was OK</returns>
        public bool SendSync(string response, bool endLine)
        {
            try
            {
                if (string.IsNullOrEmpty(response)) return true;

                var sendBuffer = (endLine)
                    ? NetVirtualTerminal.TerminalEncoding.GetBytes(response + NetVirtualTerminal.EndOfLine)
                    : NetVirtualTerminal.TerminalEncoding.GetBytes(response);
                if (sendBuffer == null || sendBuffer.Length < 1)
                {
                    return false;
                }
                Traffic.SendSync(sendBuffer);
                return true;
            }
            catch
            {
                return false;
            }
        }

        public bool SendAsync(string response, bool endLine)
        {
            try
            {
                if (string.IsNullOrEmpty(response)) return true;

                var sendBuffer = (endLine)
                    ? NetVirtualTerminal.TerminalEncoding.GetBytes(response + NetVirtualTerminal.EndOfLine)
                    : NetVirtualTerminal.TerminalEncoding.GetBytes(response);
                if (sendBuffer == null || sendBuffer.Length < 1)
                {
                    return false;
                }
                Traffic.SendAsync(sendBuffer);
                return true;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        ///     发送功能键 F1-12 到服务器
        /// </summary>
        public bool SendFunctionKey(FunctionKeys functionkey, bool sendEndOfLine)
        {
            return SendSync(FunctionKeys.F1, sendEndOfLine);
        }

        /// <summary>
        ///     退出登录
        /// </summary>
        /// <param name="synchronous">true 同步模式发送指令</param>
        /// <returns></returns>
        public bool SendLogout(bool synchronous)
        {
            byte[] logoutCmdSerial = { TelnetCommand.IAC.ByteValue(), TelnetCommand.WILL.ByteValue(), TelnetOption.Logout.ByteValue() };
            try
            {
                if (synchronous)
                {
                    Traffic.SendSync(logoutCmdSerial);
                }
                else
                {
                    Traffic.SendAsync(logoutCmdSerial);
                }
                return true;
            }
            catch
            {
                return false;
            }
        }

        #endregion


        #region 查找数据

        /// <summary>
        ///     查找数据
        /// </summary>
        /// <param name="findString">标记字符</param>
        /// <param name="caseSensitive">大小写敏感</param>
        /// <returns></returns>
        protected string FindData(string findString, bool caseSensitive)
        {
            lock (NetVirtualTerminal)
            {
                var originDataStr = TelnetData.ToString();
                var tmp = caseSensitive ? originDataStr.ToLowerInvariant() : originDataStr;
                var index = caseSensitive
                    ? tmp.IndexOf(findString.ToLowerInvariant(), StringComparison.Ordinal) : tmp.IndexOf(findString, StringComparison.Ordinal);
                if (index < 0) return null;

                return caseSensitive ? findString : originDataStr.Substring(index, findString.Length);
            }
        }

        /// <summary>
        ///     从Telnet数据中找到一个跟指定正则表达式匹配的结果
        /// </summary>
        /// <param name="regExp">用于匹配的正则式</param>
        /// <param name="caseSensitive">大小写敏感？</param>
        /// <returns>string found</returns>
        private string FindRegEx(string regExp, bool caseSensitive = false)
        {
            if (string.IsNullOrEmpty(regExp)) return null;
            lock (NetVirtualTerminal)
            {
                var reg = caseSensitive ? new Regex(regExp) : new Regex(regExp, RegexOptions.IgnoreCase);
                var match = reg.Match(TelnetData.ToString());

                return match != null && match.Success ? match.Value : null;
            }
        }

        #endregion


        #region 等待指定的关键字方法

        /// <summary>
        ///     等待一个特定的字符串
        /// </summary>
        /// <param name="searchFor">大小写敏感？</param>
        public string WaitForString(string searchFor)
        {
            return WaitForString(searchFor, false, _receiveTimeOut);
        }

        /// <summary>
        ///     等待一个特定的字符串
        /// </summary>
        /// <param name="searchFor">要查找的字符串</param>
        /// <param name="caseSensitive">大小写敏感？</param>
        /// <param name="timeoutSeconds">查找超时 [s]</param>
        /// <returns>没找到返回NULL 找到就返回找到的字符串</returns>
        public string WaitForString(string searchFor, bool caseSensitive, int timeoutSeconds)
        {
            if (string.IsNullOrEmpty(searchFor)) return null;

            //线程 睡眠 时间
            var sleepTimeMs = GetWaitSleepTimeMs(timeoutSeconds);

            //超时时间
            var endTime = TimeoutAbsoluteTime(timeoutSeconds);
            do
            {
                string found;
                lock (NetVirtualTerminal)
                {
                    found = FindData(searchFor, caseSensitive);
                }
                if (found != null) return found;
                Thread.Sleep(sleepTimeMs);
            } while (DateTime.Now <= endTime);
            return null;
        }

        /// <summary>
        ///     等待一个正则表达式匹配项
        /// </summary>
        /// <param name="regEx">要匹配的表达式</param>
        public string WaitForRegEx(string regEx)
        {
            return WaitForRegEx(regEx, _receiveTimeOut);
        }

        /// <summary>
        ///     等待一个正则表达式匹配项
        /// </summary>
        /// <param name="regEx">要匹配的表达式</param>
        /// <param name="timeoutSeconds">超时时间秒 [s]</param>
        public string WaitForRegEx(string regEx, int timeoutSeconds)
        {
            if (string.IsNullOrEmpty(regEx)) return null;

            var sleepTimeMs = GetWaitSleepTimeMs(timeoutSeconds);
            var endTime = TimeoutAbsoluteTime(timeoutSeconds);

            do
            {
                string found;
                lock (NetVirtualTerminal)
                {
                    found = FindRegEx(regEx);
                }
                if (found != null) return found;
                Thread.Sleep(sleepTimeMs);
            } while (DateTime.Now <= endTime);

            return null;
        }

        /// <summary>
        ///     Helper method:
        ///     Get the appropriate timeout, which is the bigger number of
        ///     timeoutSeconds and timeoutReceive (TCP client timeout)
        /// </summary>
        /// <param name="timeoutSeconds">timeout in seconds</param>
        private int GetWaitTimeout(int timeoutSeconds)
        {
            if (timeoutSeconds < 0 && _receiveTimeOut < 0) return 0;
            if (timeoutSeconds < 0) return _receiveTimeOut;
            return (timeoutSeconds >= _receiveTimeOut) ? timeoutSeconds : _receiveTimeOut;
        }

        /// <summary>
        ///     Helper method:
        ///     Get the appropriate sleep time based on timeout and TRIAL
        /// </summary>
        /// <param name="timeoutSeconds">timeout ins seconds</param>
        private int GetWaitSleepTimeMs(int timeoutSeconds)
        {
            return (GetWaitTimeout(timeoutSeconds) * 1000) / TelnetOptionQualifier.TRAILS.ByteValue();
        }

        /// <summary>
        ///     Helper method:
        ///     Get the end time, which is "NOW" + timeout
        /// </summary>
        /// <param name="timeoutSeconds">timeout int seconds</param>
        private DateTime TimeoutAbsoluteTime(int timeoutSeconds)
        {
            return DateTime.Now.AddSeconds(GetWaitTimeout(timeoutSeconds));
        }

        #endregion
    }

    [Serializable]
    public class TelnetException : ApplicationException
    {
        public TelnetException(string msg)
            : base(msg)
        {
        }

        public TelnetException(string msg, Exception innerException)
            : base(msg, innerException)
        {
        }
    }
}